import type { WatchStopHandle } from 'vue'
import type { Base } from './types/base'
import type { Binder } from './types/binder'
import { useWebsocketListener, type WebsocketHandlerImp } from '@/utils/socket/WebSocketHandlerImp'

export class ApplianceBinder<
  TStore extends Base.FullStore,
  TListItem extends Record<string, any>,
  TObserve = TListItem[]
> implements Binder.ApplianceBinder<TStore, TListItem, TObserve>
{
  private options!: Binder.BinderOptions<TStore, TListItem, TObserve>
  private store!: TStore

  // 监听
  watchStopHandle: WatchStopHandle | null = null

  // 使用字段
  private usingKeyProp!: Binder.PropertyName
  private usingValueProp!: Binder.PropertyName

  // 单Key绑定
  private bindings: Binder.OneByOneBindings<TStore, TListItem, TObserve> = new Map()
  private ignoreListKeys = new Set<string>()
  private ignoreMessageKeys = new Set<string>()

  // 条件绑定
  private conditionBindings: Binder.ConditionBindings<TStore, TListItem, TObserve> = []

  constructor(options: Binder.BinderOptions<TStore, TListItem, TObserve>) {
    this.options = options
    this.store = options.store
    this.usingKeyProp = options.keyProp || 'key'
    this.usingValueProp = options.valueProp || 'value'

    options.websocket && this.useWebsocketMessage(options.websocket)
  }

  addBinding(
    key: Binder.FeatureKey,
    patcher: Binder.StatePatcher<TStore, TListItem, TObserve>,
    useMessage: Binder.UseMessage
  ) {
    // 添加数组监听
    this.bindings.set(key, patcher)
    // 添加忽略list绑定修改的key
    useMessage === 'only' && this.ignoreListKeys.add(key)
    // 添加忽略message绑定修改的key
    useMessage === 'ignore' && this.ignoreMessageKeys.add(key)
  }
  addConditionBinding(keys: Binder.FeatureKey[], patcher: Binder.ConditionStatePatcher<TStore, TListItem, TObserve>) {
    this.conditionBindings.push({ keys, patcher })
  }

  startBinding() {
    const { trigger, listGetter } = this.options
    const usingListGetter = typeof listGetter === 'function'

    this.watchStopHandle = watch(
      trigger,
      (observe) => {
        if (!this.bindings.size && !this.conditionBindings.length) return

        const list = (usingListGetter ? this.options.listGetter?.(observe) : observe) as TListItem[]
        if (!Array.isArray(list)) {
          throw new Error('Binding Fatal: Observed item is not an Array')
        }

        this.handleDataPatch(observe, list)
      },
      { immediate: true }
    )
  }
  stopBinding() {
    typeof this.watchStopHandle === 'function' && this.watchStopHandle()
    this.bindings.clear()
    this.conditionBindings.splice(0)
  }

  // 处理数据
  private handleDataPatch(observe: TObserve, list: TListItem[]) {
    // 条件处理
    const conditionRefs: Binder.ConditionBindingReferences<TListItem, TObserve> = new Map()

    // 获取遍历patch后的数据
    const patchState = this.getPatchState(observe, list, conditionRefs, 'list')

    // 调用条件绑定处理函数
    this.conditionBindings.forEach(({ keys, patcher }) => {
      const references = keys.map((key) => conditionRefs.get(key) || null)
      Array.isArray(references) && patcher(patchState, references, 'list')
    })

    this.store.$patch(patchState)
  }

  private useWebsocketMessage(websocket: WebsocketHandlerImp) {
    useWebsocketListener(websocket, (msg) => {
      if (!this.store._datasource.haId || msg.haId !== this.store._datasource.haId || !Array.isArray(msg.items)) return
      // 获取遍历patch后的数据
      const observe = msg as unknown as TObserve
      const list = msg.items as unknown as TListItem[]
      const patchState = this.getPatchState(observe, list, null, 'message')
      this.store.$patch(patchState)
    })
  }

  private getPatchState(
    observe: TObserve,
    list: TListItem[],
    conditionRefs: Binder.ConditionBindingReferences<TListItem, TObserve> | null,
    invoker: Binder.PatchInvoker
  ): Binder.PatchState<TStore> {
    const patchState = list.reduce<Binder.PatchState<TStore>>((patchState, item, index) => {
      const key = this.getItemKey(item)

      if (invoker === 'list' && this.ignoreListKeys.has(key)) return patchState
      if (invoker === 'message' && this.ignoreMessageKeys.has(key)) return patchState

      const value = this.getItemValue(item)
      const reference: Binder.BindingReference<TListItem, TObserve> = { value, item, index, list, observe }

      // 调用单个绑定处理函数
      const statePatcher = this.bindings.get(key)
      statePatcher && statePatcher(patchState, reference, invoker)

      if (conditionRefs) {
        // 保存到条件处理
        conditionRefs.set(key, reference)
      }

      return patchState
    }, {})
    return patchState
  }

  private getItemKey(item: TListItem) {
    return item[this.usingKeyProp]
  }
  private getItemValue(item: TListItem) {
    return item[this.usingValueProp]
  }
}
